"""
Redis工具类
封装Redis常用操作
"""

import json
import pickle
from datetime import timedelta
from typing import Any, Optional, Union
from flask import current_app
from app.extensions import redis_client
from config.redis_config import RedisConfig


class RedisUtils:
    """Redis工具类，封装常用操作"""
    
    @staticmethod
    def is_connected() -> bool:
        """检查Redis连接是否正常"""
        try:
            redis_client.ping()
            return True
        except Exception:
            return False
    
    @staticmethod
    def set(key: str, value: Any, timeout: Optional[int] = None) -> bool:
        """
        设置键值对
        
        Args:
            key: 键名
            value: 值（可以是字符串、数字、字典、列表等）
            timeout: 过期时间（秒），默认使用配置的默认超时时间
            
        Returns:
            bool: 设置是否成功
        """
        try:
            # 如果没有指定超时时间，使用默认值
            if timeout is None:
                timeout = RedisConfig.CACHE_DEFAULT_TIMEOUT
                
            # 如果值是复杂类型，序列化为JSON
            if not isinstance(value, (str, bytes, int, float)):
                value = json.dumps(value, ensure_ascii=False)
                
            return redis_client.setex(key, timeout, value)
        except Exception as e:
            current_app.logger.error(f"Redis设置键值对失败: {e}")
            return False
    
    @staticmethod
    def get(key: str, default: Any = None) -> Any:
        """
        获取键值
        
        Args:
            key: 键名
            default: 默认值
            
        Returns:
            Any: 键对应的值，如果不存在则返回默认值
        """
        try:
            value = redis_client.get(key)
            if value is None:
                return default
                
            # 尝试解析JSON
            try:
                return json.loads(value)
            except (json.JSONDecodeError, TypeError):
                # 如果不是JSON，直接返回
                return value.decode('utf-8') if isinstance(value, bytes) else value
        except Exception as e:
            current_app.logger.error(f"Redis获取键值失败: {e}")
            return default
    
    @staticmethod
    def delete(key: str) -> bool:
        """
        删除键
        
        Args:
            key: 键名
            
        Returns:
            bool: 删除是否成功
        """
        try:
            return bool(redis_client.delete(key))
        except Exception as e:
            current_app.logger.error(f"Redis删除键失败: {e}")
            return False
    
    @staticmethod
    def exists(key: str) -> bool:
        """
        检查键是否存在
        
        Args:
            key: 键名
            
        Returns:
            bool: 键是否存在
        """
        try:
            return bool(redis_client.exists(key))
        except Exception as e:
            current_app.logger.error(f"Redis检查键是否存在失败: {e}")
            return False
    
    @staticmethod
    def expire(key: str, timeout: int) -> bool:
        """
        设置键的过期时间
        
        Args:
            key: 键名
            timeout: 过期时间（秒）
            
        Returns:
            bool: 设置是否成功
        """
        try:
            return bool(redis_client.expire(key, timeout))
        except Exception as e:
            current_app.logger.error(f"Redis设置过期时间失败: {e}")
            return False
    
    @staticmethod
    def ttl(key: str) -> int:
        """
        获取键的剩余生存时间
        
        Args:
            key: 键名
            
        Returns:
            int: 剩余时间（秒），-1表示永不过期，-2表示键不存在
        """
        try:
            return redis_client.ttl(key)
        except Exception as e:
            current_app.logger.error(f"Redis获取TTL失败: {e}")
            return -2
    
    @staticmethod
    def incr(key: str, amount: int = 1) -> Optional[int]:
        """
        将键中存储的数字值加一
        
        Args:
            key: 键名
            amount: 增加的量，默认为1
            
        Returns:
            int: 增加后的值，失败返回None
        """
        try:
            return redis_client.incrby(key, amount)
        except Exception as e:
            current_app.logger.error(f"Redis增加数值失败: {e}")
            return None
    
    @staticmethod
    def decr(key: str, amount: int = 1) -> Optional[int]:
        """
        将键中存储的数字值减一
        
        Args:
            key: 键名
            amount: 减少的量，默认为1
            
        Returns:
            int: 减少后的值，失败返回None
        """
        try:
            return redis_client.decrby(key, amount)
        except Exception as e:
            current_app.logger.error(f"Redis减少数值失败: {e}")
            return None
    
    @staticmethod
    def hset(name: str, mapping: dict, timeout: Optional[int] = None) -> bool:
        """
        设置哈希表字段值
        
        Args:
            name: 哈希表名
            mapping: 字段和值的映射
            timeout: 过期时间（秒）
            
        Returns:
            bool: 设置是否成功
        """
        try:
            # 序列化复杂类型的值
            serialized_mapping = {}
            for key, value in mapping.items():
                # 确保键是字符串
                key_str = str(key)
                
                # 序列化复杂类型的值
                if isinstance(value, (dict, list, tuple)):
                    serialized_mapping[key_str] = json.dumps(value, ensure_ascii=False)
                elif isinstance(value, bool):
                    serialized_mapping[key_str] = str(value)
                else:
                    serialized_mapping[key_str] = str(value)
            
            # 设置哈希表
            result = redis_client.hset(name, mapping=serialized_mapping)
            
            # 设置过期时间
            if timeout is not None:
                redis_client.expire(name, timeout)
                
            return result >= 0
        except Exception as e:
            current_app.logger.error(f"Redis设置哈希表失败: {e}")
            return False
    
    @staticmethod
    def hget(name: str, key: str) -> Any:
        """
        获取哈希表中指定字段的值
        
        Args:
            name: 哈希表名
            key: 字段名
            
        Returns:
            Any: 字段值
        """
        try:
            value = redis_client.hget(name, key)
            if value is None:
                return None
                
            # 尝试解析JSON
            try:
                return json.loads(value)
            except (json.JSONDecodeError, TypeError):
                # 如果不是JSON，直接返回
                return value.decode('utf-8') if isinstance(value, bytes) else value
        except Exception as e:
            current_app.logger.error(f"Redis获取哈希表字段失败: {e}")
            return None
    
    @staticmethod
    def hgetall(name: str) -> dict:
        """
        获取哈希表中所有字段和值
        
        Args:
            name: 哈希表名
            
        Returns:
            dict: 所有字段和值的字典
        """
        try:
            data = redis_client.hgetall(name)
            result = {}
            
            for key, value in data.items():
                # 解码键
                key_str = key.decode('utf-8') if isinstance(key, bytes) else key
                
                # 尝试解析值
                try:
                    result[key_str] = json.loads(value)
                except (json.JSONDecodeError, TypeError):
                    result[key_str] = value.decode('utf-8') if isinstance(value, bytes) else value
                    
            return result
        except Exception as e:
            current_app.logger.error(f"Redis获取哈希表所有字段失败: {e}")
            return {}
    
    @staticmethod
    def sadd(name: str, *values) -> Optional[int]:
        """
        向集合添加成员
        
        Args:
            name: 集合名
            values: 要添加的成员
            
        Returns:
            int: 添加的成员数量，失败返回None
        """
        try:
            # 序列化复杂类型
            serialized_values = []
            for value in values:
                if not isinstance(value, (str, bytes)):
                    serialized_values.append(json.dumps(value, ensure_ascii=False))
                else:
                    serialized_values.append(value)
                    
            return redis_client.sadd(name, *serialized_values)
        except Exception as e:
            current_app.logger.error(f"Redis向集合添加成员失败: {e}")
            return None
    
    @staticmethod
    def smembers(name: str) -> set:
        """
        获取集合中的所有成员
        
        Args:
            name: 集合名
            
        Returns:
            set: 集合中的所有成员
        """
        try:
            members = redis_client.smembers(name)
            result = set()
            
            for member in members:
                # 尝试解析JSON
                try:
                    result.add(json.loads(member))
                except (json.JSONDecodeError, TypeError):
                    # 如果不是JSON，直接返回
                    result.add(member.decode('utf-8') if isinstance(member, bytes) else member)
                    
            return result
        except Exception as e:
            current_app.logger.error(f"Redis获取集合成员失败: {e}")
            return set()
    
    @staticmethod
    def sismember(name: str, value) -> bool:
        """
        判断成员是否在集合中
        
        Args:
            name: 集合名
            value: 成员值
            
        Returns:
            bool: 成员是否在集合中
        """
        try:
            # 序列化复杂类型
            if not isinstance(value, (str, bytes)):
                value = json.dumps(value, ensure_ascii=False)
                
            return bool(redis_client.sismember(name, value))
        except Exception as e:
            current_app.logger.error(f"Redis判断集合成员失败: {e}")
            return False
    
    @staticmethod
    def lpush(name: str, *values) -> Optional[int]:
        """
        将一个或多个值插入到列表头部
        
        Args:
            name: 列表名
            values: 要插入的值
            
        Returns:
            int: 列表长度，失败返回None
        """
        try:
            # 序列化复杂类型
            serialized_values = []
            for value in values:
                if not isinstance(value, (str, bytes)):
                    serialized_values.append(json.dumps(value, ensure_ascii=False))
                else:
                    serialized_values.append(value)
                    
            return redis_client.lpush(name, *serialized_values)
        except Exception as e:
            current_app.logger.error(f"Redis列表头部插入失败: {e}")
            return None
    
    @staticmethod
    def rpush(name: str, *values) -> Optional[int]:
        """
        将一个或多个值插入到列表尾部
        
        Args:
            name: 列表名
            values: 要插入的值
            
        Returns:
            int: 列表长度，失败返回None
        """
        try:
            # 序列化复杂类型
            serialized_values = []
            for value in values:
                if not isinstance(value, (str, bytes)):
                    serialized_values.append(json.dumps(value, ensure_ascii=False))
                else:
                    serialized_values.append(value)
                    
            return redis_client.rpush(name, *serialized_values)
        except Exception as e:
            current_app.logger.error(f"Redis列表尾部插入失败: {e}")
            return None
    
    @staticmethod
    def lpop(name: str) -> Any:
        """
        移出并获取列表的第一个元素
        
        Args:
            name: 列表名
            
        Returns:
            Any: 列表的第一个元素
        """
        try:
            value = redis_client.lpop(name)
            if value is None:
                return None
                
            # 尝试解析JSON
            try:
                return json.loads(value)
            except (json.JSONDecodeError, TypeError):
                # 如果不是JSON，直接返回
                return value.decode('utf-8') if isinstance(value, bytes) else value
        except Exception as e:
            current_app.logger.error(f"Redis弹出列表头部元素失败: {e}")
            return None
    
    @staticmethod
    def rpop(name: str) -> Any:
        """
        移出并获取列表的最后一个元素
        
        Args:
            name: 列表名
            
        Returns:
            Any: 列表的最后一个元素
        """
        try:
            value = redis_client.rpop(name)
            if value is None:
                return None
                
            # 尝试解析JSON
            try:
                return json.loads(value)
            except (json.JSONDecodeError, TypeError):
                # 如果不是JSON，直接返回
                return value.decode('utf-8') if isinstance(value, bytes) else value
        except Exception as e:
            current_app.logger.error(f"Redis弹出列表尾部元素失败: {e}")
            return None
    
    @staticmethod
    def llen(name: str) -> int:
        """
        获取列表长度
        
        Args:
            name: 列表名
            
        Returns:
            int: 列表长度
        """
        try:
            return redis_client.llen(name)
        except Exception as e:
            current_app.logger.error(f"Redis获取列表长度失败: {e}")
            return 0
    
    @staticmethod
    def lrange(name: str, start: int = 0, end: int = -1) -> list:
        """
        获取列表指定范围内的元素
        
        Args:
            name: 列表名
            start: 起始索引
            end: 结束索引
            
        Returns:
            list: 列表指定范围内的元素
        """
        try:
            values = redis_client.lrange(name, start, end)
            result = []
            
            for value in values:
                # 尝试解析JSON
                try:
                    result.append(json.loads(value))
                except (json.JSONDecodeError, TypeError):
                    # 如果不是JSON，直接返回
                    result.append(value.decode('utf-8') if isinstance(value, bytes) else value)
                    
            return result
        except Exception as e:
            current_app.logger.error(f"Redis获取列表范围失败: {e}")
            return []
    
    @staticmethod
    def set_object(key: str, obj: Any, timeout: Optional[int] = None) -> bool:
        """
        使用pickle序列化对象并存储
        
        Args:
            key: 键名
            obj: 要存储的对象
            timeout: 过期时间（秒）
            
        Returns:
            bool: 设置是否成功
        """
        try:
            # 如果没有指定超时时间，使用默认值
            if timeout is None:
                timeout = RedisConfig.CACHE_DEFAULT_TIMEOUT
                
            # 使用pickle序列化对象
            serialized_obj = pickle.dumps(obj)
            return redis_client.setex(key, timeout, serialized_obj)
        except Exception as e:
            current_app.logger.error(f"Redis设置对象失败: {e}")
            return False
    
    @staticmethod
    def get_object(key: str, default: Any = None) -> Any:
        """
        获取并反序列化对象
        
        Args:
            key: 键名
            default: 默认值
            
        Returns:
            Any: 反序列化后的对象
        """
        try:
            serialized_obj = redis_client.get(key)
            if serialized_obj is None:
                return default
                
            # 使用pickle反序列化对象
            return pickle.loads(serialized_obj)
        except Exception as e:
            current_app.logger.error(f"Redis获取对象失败: {e}")
            return default
    
    @staticmethod
    def cache_user_session(user_id: int, session_data: dict) -> bool:
        """
        缓存用户会话数据
        
        Args:
            user_id: 用户ID
            session_data: 会话数据
            
        Returns:
            bool: 缓存是否成功
        """
        key = f"user:session:{user_id}"
        return RedisUtils.set(key, session_data, RedisConfig.SESSION_TIMEOUT)
    
    @staticmethod
    def get_user_session(user_id: int) -> Optional[dict]:
        """
        获取用户会话数据
        
        Args:
            user_id: 用户ID
            
        Returns:
            dict: 会话数据，如果不存在则返回None
        """
        key = f"user:session:{user_id}"
        return RedisUtils.get(key)
    
    @staticmethod
    def clear_user_session(user_id: int) -> bool:
        """
        清除用户会话数据
        
        Args:
            user_id: 用户ID
            
        Returns:
            bool: 清除是否成功
        """
        key = f"user:session:{user_id}"
        return RedisUtils.delete(key)